package jeeobserver.server;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

class DatabaseFile<T extends DatabaseFile.DatabaseEntity> {

	 public static final byte STATUS_NEW = 1;

	 public static final byte STATUS_OPEN = 2;

	 public static final byte STATUS_CLOSED = 3;

	 private RandomAccessFile file;

	 private String name;

	 private FileLock fileLock;

	 private Class<T> type;

	 private int recordLenght;

	 public void setType(Class<T> type) {
			this.type = type;
	 }

	 DatabaseFile(String name, Class<T> type, int recordLenght) throws IOException {

			this.file = new RandomAccessFile(name, "rwd");

			this.name = name;

			this.type = type;

			this.recordLenght = recordLenght;

			//TODO
			//this.fileLock = this.file.getChannel().tryLock();

			if (this.file.length() < HeaderEntity.BYTES_SIZE) {
				 this.file.write(new HeaderEntity().getBytes());
			}

			this.file.seek(0);
			this.file.write(STATUS_OPEN);

			this.file.seek(0);
	 }

	 public void reset() throws IOException {
			this.file.seek(HeaderEntity.BYTES_SIZE);
	 }

	 public void rename(String destination) throws IOException {
			this.close();
			new File(this.name).renameTo(new File(destination));
	 }

	 public long getPointer() throws IOException {
			return this.file.getFilePointer();
	 }

	 public void seek(long position) throws IOException {
			this.file.seek(position);
	 }

	 public void setLength(long length) throws IOException {
			this.file.setLength(length);
	 }

	 public long length() throws IOException {
			return this.file.length();
	 }

	 public void delete() throws IOException {
			this.close();
			new File(this.name).delete();
	 }

	 public void clear() throws IOException {
			this.file.setLength(HeaderEntity.BYTES_SIZE);
	 }

	 public String getName() {
			return name;
	 }

	 public Class<T> getType() {
			return type;
	 }

	 public int getRecordLenght() {
			return recordLenght;
	 }

	 public synchronized List<T> readAll() throws IOException {

			this.reset();

			List<T> results = new ArrayList<T>();

			T entity;

			while ((entity = this.read()) != null) {
				 results.add(entity);
			}

			return results;
	 }

	 public synchronized Collection<T> read(int size) throws IOException {

			List<T> result = new ArrayList<T>();

			byte bytes[] = new byte[recordLenght * size];

			int read = this.file.read(bytes);

			if (read > 0) {
				 ByteBuffer buffer = ByteBuffer.wrap(bytes, 0, read);

				 while (buffer.hasRemaining()) {
						byte current[] = new byte[recordLenght];
						buffer.get(current);

						try {
							 result.add(type.getDeclaredConstructor(byte[].class).newInstance(current));
						} catch (Exception e) {
							 throw new IOException(e);
						}
				 }
			}

			//System.out.println("TOTAL: " + result.size());

			return result;
	 }

	 public synchronized <type> T read() throws IOException {
			try {
				 //entityClass entity = (entityClass) entityClass.getConstructor(String.class).newInstance();
				 //entityClass entity = (entityClass) entityClass.newInstance();

				 byte bytes[];
				 if (recordLenght > 0) {
						bytes = new byte[recordLenght];
				 } else {
						if (this.file.length() - this.file.getFilePointer() > 4) {
							 bytes = new byte[this.file.readInt()];
							 //this.file.seek(this.file.getFilePointer() - 4);
						} else {
							 bytes = new byte[0];
						}
				 }

				 if (bytes.length > 0 && this.file.read(bytes) == bytes.length) {
						//((DatabaseEntity) entity).setBytes(bytes);
						//entity = (entityClass)
						//ByteBuffer buffer = ByteBuffer.wrap(bytes);
						return type.getDeclaredConstructor(byte[].class).newInstance(bytes);
				 }
			} catch (Exception e) {
				 throw new IOException(e);
			}

			return null;
	 }

	 public synchronized void write(Collection<T> entities) throws IOException {

			if (entities.isEmpty()) {
				 return;
			}

			int bufferSize = 0;

			if (recordLenght == 0) {
				 for (T entity : entities) {
						bufferSize = 4 + entity.getSize();
				 }
			} else {
				 bufferSize = entities.size() * this.recordLenght;
			}

			ByteBuffer buffer = ByteBuffer.allocate(bufferSize);

			for (T entity : entities) {
				 if (this.recordLenght == 0) {
						this.file.writeInt(entity.getBytes().length);
				 }

				 buffer.put(entity.getBytes());
			}

			this.file.write(buffer.array());
	 }

	 public synchronized void write(T entity) throws IOException {
			if (recordLenght == 0) {
				 this.file.writeInt(entity.getBytes().length);
			}
			this.file.write(entity.getBytes());
	 }

	 public synchronized void write(byte bytes[]) throws IOException {
			this.file.write(bytes);
	 }

	 public synchronized void read(byte bytes[]) throws IOException {
			this.file.read(bytes);
	 }

	 public synchronized void writePervious(T entity) throws IOException {
			this.file.seek(this.file.getFilePointer() - entity.getSize());
			this.file.write(entity.getBytes());
	 }

	 public synchronized long append(T entity) throws IOException {

			this.file.seek(this.file.length());

			long position = this.file.getFilePointer();

			this.write(entity);

			return position;
	 }

	 public byte getStatus() throws IOException {
			this.file.seek(0);

			return this.file.readByte();
	 }

	 public byte[] getVersion() throws IOException {
			this.file.seek(1);

			byte version[] = new byte[3];

			this.file.read(version);

			return version;
	 }

	 protected void close() throws IOException {

			if (this.fileLock != null) {
				 this.fileLock.release();
			}

			this.file.seek(0);
			this.file.write(STATUS_CLOSED);

			this.file.close();
	 }

	 static class DatabaseFileFixedRecord<T> extends DatabaseFile {

			DatabaseFileFixedRecord(String path, Class<T> type, int recordLenght) throws IOException {
				 super(path, type, recordLenght);
			}
	 }

	 static class DatabaseFileVariableRecord<T> extends DatabaseFile {

			DatabaseFileVariableRecord(String path, Class<T> type) throws IOException {
				 super(path, type, 0);
			}
	 }

	 public interface DatabaseEntity {

			public byte[] getBytes();

			public int getSize();
	 }

	 public static class HeaderEntity implements DatabaseEntity {

			public static final int BYTES_SIZE = 1024;

			private byte status;

			private Date creationDate;

			private long size;

			public HeaderEntity(ByteBuffer buffer) {
				 this.status = buffer.get();
				 this.creationDate = new Date(buffer.getLong());
			}

			public HeaderEntity(byte[] bytes) {
				 this(ByteBuffer.wrap(bytes));
			}

			public HeaderEntity() {
				 this.status = 0;
				 this.creationDate = new Date();
			}

			@Override
			public byte[] getBytes() {

				 ByteBuffer buffer = ByteBuffer.allocate(BYTES_SIZE);

				 buffer.put(this.status);

				 buffer.putLong(this.creationDate.getTime());

				 return buffer.array();
			}

			@Override
			public int getSize() {
				 return BYTES_SIZE;
			}

			public byte getStatus() {
				 return status;
			}

			public Date getCreationDate() {
				 return creationDate;
			}

			public void setStatus(byte status) {
				 this.status = status;
			}

			public void setCreationDate(Date creationDate) {
				 this.creationDate = creationDate;
			}
	 }

	 static class DatabaseFileBinary<T> extends DatabaseFile.DatabaseFileVariableRecord {

			DatabaseFileBinary(String path, Class<T> type) throws IOException {
				 super(path + ".dat", type);
			}
	 }
}
